"use strict";

const chalk = require("chalk");

if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
    // Inside the class equivalence tester, we have a custom environment, let's
    // require that instead.
    require("./spec-equivalence-reporter/setupTests.js");
} else {
    const env = jasmine.getEnv();
    const errorMap = require("./codes.json");

    // TODO: Stop using spyOn in all the test since that seem deprecated.
    // This is a legacy upgrade path strategy from:
    // https://github.com/facebook/jest/blob/v20.0.4/packages/jest-matchers/src/spyMatchers.js#L160
    const isSpy = spy => spy.calls && typeof spy.calls.count === "function";

    const spyOn = global.spyOn;
    const noop = function() {};

    // Spying on console methods in production builds can mask errors.
    // This is why we added an explicit spyOnDev() helper.
    // It's too easy to accidentally use the more familiar spyOn() helper though,
    // So we disable it entirely.
    // Spying on both dev and prod will require using both spyOnDev() and spyOnProd().
    global.spyOn = function() {
        throw new Error(
            "Do not use spyOn(). " +
        "It can accidentally hide unexpected errors in production builds. " +
        "Use spyOnDev(), spyOnProd(), or spyOnDevAndProd() instead."
        );
    };

    if (process.env.NODE_ENV === "production") {
        global.spyOnDev = noop;
        global.spyOnProd = spyOn;
        global.spyOnDevAndProd = spyOn;
    } else {
        global.spyOnDev = spyOn;
        global.spyOnProd = noop;
        global.spyOnDevAndProd = spyOn;
    }

    expect.extend(Object.assign({},require("./matchers/toWarnDev")));

    // We have a Babel transform that inserts guards against infinite loops.
    // If a loop runs for too many iterations, we throw an error and set this
    // global variable. The global lets us detect an infinite loop even if
    // the actual error object ends up being caught and ignored. An infinite
    // loop must always fail the test!
    env.beforeEach(() => {
        global.infiniteLoopError = null;
    });
    env.afterEach(() => {
        const error = global.infiniteLoopError;
        global.infiniteLoopError = null;
        if (error) {
            throw error;
        }
    });

    ["error", "warn"].forEach(methodName => {
        const unexpectedConsoleCallStacks = [];
        const newMethod = function(message) {
            // Capture the call stack now so we can warn about it later.
            // The call stack has helpful information for the test author.
            // Don't throw yet though b'c it might be accidentally caught and suppressed.
            const stack = new Error().stack;
            unexpectedConsoleCallStacks.push([
                stack.substr(stack.indexOf("\n") + 1),
                message,
            ]);
        };
       
        console[methodName] = newMethod;

        env.beforeEach(() => {
            unexpectedConsoleCallStacks.length = 0;
        });

        env.afterEach(() => {
            if (console[methodName] !== newMethod && !isSpy(console[methodName])) {
                throw new Error(
                    `Test did not tear down console.${methodName} mock properly.`
                );
            }

            if (unexpectedConsoleCallStacks.length > 0) {
                const messages = unexpectedConsoleCallStacks.map(
                    ([stack, message]) =>
                        `${chalk.red(message)}\n` +
            `${stack
                .split("\n")
                .map(line => chalk.gray(line))
                .join("\n")}`
                );

                const message =
          `Expected test not to call ${chalk.bold(
              `console.${methodName}()`
          )}.\n\n` +
          "If the warning is expected, test for it explicitly by:\n" +
          `1. Using the ${chalk.bold(".toWarnDev()")} / ${chalk.bold(
              ".toLowPriorityWarnDev()"
          )} matchers, or...\n` +
          `2. Mock it out using ${chalk.bold("spyOnDev")}(console, '${
              methodName
          }') or ${chalk.bold("spyOnProd")}(console, '${
              methodName
          }'), and test that the warning occurs.`;

                throw new Error(`${message}\n\n${messages.join("\n\n")}`);
            }
        });
    });
    if (process.env.NODE_ENV === "production") {
    // In production, we strip error messages and turn them into codes.
    // This decodes them back so that the test assertions on them work.
        const decodeErrorMessage = function(message) {
            if (!message) {
                return message;
            }
            const re = /error-decoder.html\?invariant=(\d+)([^\s]*)/;
            const matches = message.match(re);
            if (!matches || matches.length !== 3) {
                return message;
            }
            const code = parseInt(matches[1], 10);
            const args = matches[2]
                .split("&")
                .filter(s => s.startsWith("args[]="))
                .map(s => s.substr("args[]=".length))
                .map(decodeURIComponent);
            const format = errorMap[code];
            let argIndex = 0;
            return format.replace(/%s/g, () => args[argIndex++]);
        };
        const OriginalError = global.Error;
        const ErrorProxy = new Proxy(OriginalError, {
            apply(target, thisArg, argumentsList) {
                const error = Reflect.apply(target, thisArg, argumentsList);
                error.message = decodeErrorMessage(error.message);
                return error;
            },
            construct(target, argumentsList, newTarget) {
                const error = Reflect.construct(target, argumentsList, newTarget);
                error.message = decodeErrorMessage(error.message);
                return error;
            },
        });
        ErrorProxy.OriginalError = OriginalError;
        global.Error = ErrorProxy;
    }

    require("jasmine-check").install();
}
